【Clojure入門】 制御構文
Clojureにおける制御構文は「特殊形式」または「マクロ」として提供されています。
Lisp系言語では通常、オペレータ(operator; 演算子)は
関数(function)
マクロ(macro)
特殊形式(special form) ※正確には特殊オペレータ(special operator)とも
の3種類に分類され、 (<オペレータ> <引数1> <引数2> ...) という統一的な構文形式になっています。
「文」(statement)と「式」(expression)の両方を備えた言語も少なくありませんが、Clojureは式のみで構成された徹底した式指向の言語です。
ここでは代表的な制御構文としての特殊形式/マクロを紹介しますが、Clojure標準ライブラリにはこの他にも多彩なマクロが含まれており、さらにはマクロを自ら定義することで独自の制御構文を追加することも簡単にできます。
;; TODO
let特殊形式
code:clojure
(let <束縛式>* <本体式>*)
;; <束縛式> = <束縛フォーム> <初期値>
cf. letfn
do特殊形式
code:clojure
(do <式>*)
code:clojure
user=> (do (println "A") (println "B") (+ 1 2))
A
B
3
code:clojure
(defn foo []
(str "foo" "foo"))
「暗黙のdo」
if特殊形式
code:clojure
(if <条件式> <then式> <else式>?)
code:clojure
user=> (let age 17
#_=> (if (< age 18)
#_=> "18歳未満です"
#_=> "18歳以上です"))
"18歳未満です"
user=> (let age 18
#_=> (if (< age 18)
#_=> "18歳未満です"
#_=> "18歳以上です"))
"18歳以上です"
code:clojure
(if <条件式> <then式> nil)
cf. if-not, if-let, if-some
whenマクロ
code:clojure
(when <条件式> <then式>*)
code:clojure
(if <条件式> (do <then式>*))
cf. when-not, when-let, when-first, when-some
condマクロ
code:clojure
(cond <節>*)
;; <節> = <条件式> <then式>
cf. condp, cond->, cond->>
練習問題1
loop特殊形式
code:clojure
(loop <束縛式>* <本体式>*)
;; <束縛式> = <束縛フォーム> <初期値>
let によく似ている
recur 特殊形式と組み合わせて末尾再帰関数を簡潔に書くための構文
他言語におけるループ文などとは異なる
whileマクロ
code:clojure
(while <条件式> <本体式>*)
code:clojure
(loop []
(when <条件式>
<本体式>*
(recur)))
code:clojure
user=> (let i (atom 1)
#_=> (while (<= @i 10)
#_=> (println (str "i = " @i))
#_=> (swap! i inc)))
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
i = 10
nil
練習問題2
doseqマクロ
code:clojure
(doseq <ジェネレータ>+ <本体式>*)
;; <ジェネレータ> = <シンボル> <式>
code:clojure
user=> (doseq [x (range 1 (inc 5))
#_=> y (range 1 5)]
#_=> (println "x = " x " y = " y))
x = 1 y = 1
x = 1 y = 2
x = 1 y = 3
x = 1 y = 4
x = 2 y = 1
x = 2 y = 2
x = 2 y = 3
x = 2 y = 4
x = 3 y = 1
x = 3 y = 2
x = 3 y = 3
x = 3 y = 4
x = 4 y = 1
x = 4 y = 2
x = 4 y = 3
x = 4 y = 4
x = 5 y = 1
x = 5 y = 2
x = 5 y = 3
x = 5 y = 4
nil
code:clojure
user=> (doseq [x (range 1 (inc 5))
#_=> y (range 1 5)
#_=> :when (not= x y)]
#_=> (println "x = " x " y = " y))
x = 1 y = 2
x = 1 y = 3
x = 1 y = 4
x = 2 y = 1
x = 2 y = 3
x = 2 y = 4
x = 3 y = 1
x = 3 y = 2
x = 3 y = 4
x = 4 y = 1
x = 4 y = 2
x = 4 y = 3
x = 5 y = 1
x = 5 y = 2
x = 5 y = 3
x = 5 y = 4
nil
code:clojure
user=> (doseq [e "A" "B" "C" "D" "E"] (println e))
A
B
C
D
E
nil
cf. dotimes
forマクロ
code:clojure
(for <ジェネレータ>+ <本体式>)
;; <ジェネレータ> = <シンボル> <式>
code:clojure
user=> (for [e "A" "B" "C" "D" "E"] (str "Pre" e))
("PreA" "PreB" "PreC" "PreD" "PreE")
練習問題3
caseマクロ
code:clojure
(case <式> <節>* <デフォルト値>?)
;; <節> = <定数> <式>
code:clojure
user=> (let taro "Taro"
#_=> (case taro
#_=> "Taro" "Male"
#_=> "Jiro" "Male"
#_=> "Hanako" "Female"))
"Male"
code:clojure
user=> (let one 1
#_=> (case one
#_=> 1 "one"
#_=> 2 "two"
#_=> "other"))
"one"
code:clojure
user=> (case "abc"
#_=> "abc" (println "first")
#_=> "def" (println "second"))
first
nil
code:clojure
user=> (case "abc"
#_=> ("abc" "def") (do (println "first")
#_=> (println "second")))
first
second
nil
clojure.core.match/matchマクロ
code:clojure
(clojure.core.match/match <対象式>
<節>*)
;; <節> = (<パターン> <ガード>?) <式>
;; <ガード> = :guard <述語関数>
core.matchという準標準ライブラリ
code:project.clj
(defproject sandbox "0.1.0"
:dependencies [org.clojure/clojure "1.10.1"
org.clojure/core.match "0.3.0"] ; core.match最新版を追加
:main sandbox.hello-world
:profiles {:dev {:dependencies com.bhauman/rebel-readline "0.1.4"
:aliases {"rebel" "trampoline" "run" "-m" "rebel-readline.main"}}})
code:clojure
user=> (require '[clojure.core.match :refer match])
nil
code:clojure
user=> (let taro "Taro"
#_=> (match taro
#_=> "Taro" "Male"
#_=> "Jiro" "Male"
#_=> "Hanako" "Female"))
"Male"
user=> (let one 1
#_=> (match one
#_=> 1 "one"
#_=> 2 "two"
#_=> :else "other"))
"one"
user=> (match "abc"
#_=> "abc" (println "first")
#_=> "def" (println "second"))
first
nil
user=> (match "abc"
#_=> (:or "abc" "def") (do (println "first")
#_=> (println "second")))
first
second
nil
code:clojure
user=> (let [lst "A" "B" "C"]
#_=> (match lst
#_=> ("A" b c :seq) (do (println (str "b = " b))
#_=> (println (str "c = " c)))
#_=> :else (println "nothing")))
b = B
c = C
nil
code:clojure
user=> (let [lst "A" "B" "C"]
#_=> (match lst
#_=> ("A" (b :guard #(not= % "B")) c :seq) (do (println (str "b = " b))
#_=> (println (str "c = " c)))
#_=> :else (println "nothing")))
nothing
nil
code:clojure
user=> (let [lst "A"] "B" "C"
#_=> (match lst
#_=> ([("A" :as a :seq) x] :seq) (do (println a)
#_=> (println x))
#_=> :else (println "nothing")))
A
B C
nil
code:clojure
user=> (let [lst "A" "B" "C" "D"]
#_=> (match lst
#_=> ("A" b c & _ :seq) (do (println (str "b = " b))
#_=> (println (str "c = " c)))
#_=> :else (println "nothing")))
b = B
c = C
nil
code:clojure
user=> (import java.util.Locale)
java.util.Locale
user=> (let obj "String Literal"
#_=> (match obj
#_=> (v :guard #(instance? Integer %)) (println "Integer!")
#_=> (v :guard #(instance? String %)) (println (.toUpperCase v Locale/ENGLISH))))
STRING LITERAL
nil
;; もしくは
user=> (let [obj "String Literal"
#_=> String String
#_=> Integer Integer]
#_=> (match (type obj)
#_=> Integer (println "Integer!")
#_=> String (println (.toUpperCase obj Locale/ENGLISH))))
STRING LITERAL
nil
練習問題4
cf. Scalaの制御構文 · Scala研修テキスト
#ScalaプログラマのためのClojure入門 #Clojure